// $Id: output_impl_header.cpp 2 2009-09-02 08:47:42Z joseph2002 $
/*
 * Copyright 2009 Joseph Wu
 * Distributed under the Boost Software License, Version 1.0.
 * (See accompanying file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt)
 *
 * For more information, see http://xpcomplus.sourceforge.net
 */

#include <iostream>
#include <vector>
#include <boost/assert.hpp>
#include <boost/filesystem.hpp>
#include <boost/filesystem/fstream.hpp>
#include <boost/foreach.hpp>
#include <xpgen/output_impl_header.hpp>
#include <xpgen/output_header.hpp>
#include <xpgen/model.hpp>
#include "names.hpp"

using namespace std;
namespace fs = boost::filesystem;

namespace xpgen {

output_impl_header::output_impl_header(output* prev) :
    output_cpp(prev),
    exact_includes(false)
{
}

output_impl_header::~output_impl_header()
{
}

string output_impl_header::get_filename(interface_decl* itf) const
{
    return itf->name + N_SUFFIX_impl + "." + output_ext;
}

string output_impl_header::get_header_filename(interface_decl* itf) const
{
    output_header* p = dynamic_cast<output_header*>(prev());
    if (p)
        return p->get_filename(itf);

    return itf->name + "." + output_ext;
}

string output_impl_header::get_interface_impl_type(interface_decl* itf) const
{
    string ret;
    ret += N_interface_impl;
    ret += "<";
    ret += N_TEMPL_Class;
    ret += ", ";
    ret += get_relative_interface_qname_cpp(itf);
    ret += ", ";
    ret += N_TEMPL_Base;
    ret += ">";
    return ret;
}

void output_impl_header::do_reset()
{
    output_cpp::do_reset();

    output_header* p = dynamic_cast<output_header*>(prev());
    if (p) {
        exact_includes = p->exact_includes;
    } else {
        output_impl_header* p = dynamic_cast<output_impl_header*>(prev());
        if (p) {
            exact_includes = p->exact_includes;
        }
    }
}

void output_impl_header::do_prologue()
{
    output_cpp::do_prologue();

    if (verbose)
        cout << ">>> Writing interface implementations..." << endl;

    // Check output directory
    fs::create_directories(output_dir);
}

void output_impl_header::do_epilogue()
{
    if (verbose)
        cout << "<<< End writing interface implementations." << endl;

    output_cpp::do_epilogue();
}

void output_impl_header::do_interface(interface_decl* itf)
{
    BOOST_ASSERT(itf && itf->is_resolved() && itf->base);

    if (verbose)
        cout << "  interface implementation: " << itf->context_name() << endl;

    // Open stream for interface
    string output_filename = get_filename(itf);
    fs::path output_file = output_dir / output_filename;

    fs::ofstream os(output_file);

    // Print file header informations
    print_file_header(os, output_filename);

    // Print inclusion guard begin
    string include_guard_name = get_include_guard_name(itf, N_SUFFIX_impl);

    os << endl;
    os << indent() << "#ifndef " << include_guard_name << endl;
    os << indent() << "#define " << include_guard_name << endl;

    // Print forward interfaces
    do_interface_reference(os, itf);

    // Begin xpcom::impl namespace

    // Begin xpcom namespace
    os << endl;
    print_ns_begin(os, N_xpcom);

    // Begin xpcom::impl namespace
    os << endl;
    print_ns_begin(os, N_impl);

    // Print interface
    do_interface_def(os, itf);

    // End xpcom::impl namespace
    os << endl;
    print_ns_end(os);

    // End xpcom namespace
    os << endl;
    print_ns_end(os);

    // Print inclusion guard begin
    os << endl;
    os << indent() << "#endif // " << include_guard_name << endl;

    // Close stream
    os.close();
}

unsigned output_impl_header::get_param_include_flags(param_decl* param) const
{
    unsigned ret = 0;

    // string is always here if has string param
    if (param->type.is_array) {
        ret = param->type.is_interface() ?
                (inc_ptr_array | inc_impl_ptr_array) :
                (inc_array | inc_impl_array);
    } else if (param->type.tag == type_decl::tag_bool) {
        ret = inc_impl_bool;
    } else if (param->type.tag == type_decl::tag_uuid_ptr) {
        ret = inc_impl_uuid_ptr;
    } else if (param->type.tag == type_decl::tag_utf8string) {
        ret = inc_ustring;
    } else if (param->type.is_string() || param->type.is_string_ptr()) {
        ret = inc_string;
    } else if (param->type.is_interface()) {
        ret = inc_ptr | inc_impl_ptr;
    } else if (param->type.is_pstring()) {
        ret = inc_impl_pstring;
        // if is output type, also need string
        if (!param->type.is_array && param->has_out() && !param->is_shared())
            ret |= inc_string;
    }

    if (param_needs_check(param)) {
        ret |= inc_result_code;
        ret |= inc_assert;
    }

    return ret;
}

unsigned output_impl_header::get_include_flags(interface_decl* itf) const
{
    if (!exact_includes)
        return inc_xpcom_impl;

    unsigned inc_flags = inc_interface; // interface is needed always
    inc_flags |= inc_impl_interface_impl;

    BOOST_FOREACH(method_decl* method, itf->methods)
    {
        // If is regular method, then exception is needed
        if (!method->is_notxpcom())
            inc_flags |= (inc_exception | inc_impl_exception);

        inc_flags |= get_param_include_flags(method->result);

        BOOST_FOREACH(param_decl* param, method->params)
        {
            inc_flags |= get_param_include_flags(param);
        }
    }

    return inc_flags;
}

void output_impl_header::do_interface_reference(
        ostream& os, interface_decl* itf)
{
    os << endl;

    // Includes
    print_includes(os, get_include_flags(itf));

    os << indent() << "#include \"" << get_header_filename(itf) << "\"" << endl;

    if (!itf->base->is_root()) {
        os << indent() << "#include \""
           << get_filename(itf->base) << "\"" << endl;
    }

    // Forward
    print_interface_forward(os, itf);
}

void output_impl_header::do_interface_def(ostream& os, interface_decl* itf)
{
    os << endl;

    // class line
    os << "template <typename "
       << N_TEMPL_Class << ", typename " << N_TEMPL_Base << ">" << endl;
    os << indent() << "class " << N_XPCOM_NO_VTABLE << " "
       << get_interface_impl_type(itf) << " :"
       << endl;
    push_indent();
    os << indent() << "public " << get_interface_impl_type(itf->base)
       << endl;
    pop_indent();

    // class body
    os << indent() << "{" << endl;

    os << indent() << "public:" << endl;
    push_indent();

    // Print interface type
    os << indent() << "typedef "
       << get_relative_interface_qname_cpp(itf) << " " << N_type << ";"
       << endl;

    // Print interface impl_type
    os << indent() << "typedef "
       << get_interface_impl_type(itf) << " " << N_impl_type << ";"
       << endl;

    // Implementation methods for user
    if (!itf->methods.empty()) {
        os << endl;
        os << indent() << "// Following methods must be implemented:" << endl;
        os << indent() << "/*" << endl;
        for (size_t i = 0; i < itf->methods.size(); ++i) {
            os << indent();
            do_impl_method_signature(os, itf->methods[i]);
            os << ";" << endl;
        }
        os << indent() << "*/" << endl;
    }

    // methods
    for (size_t i = 0; i < itf->methods.size(); ++i) {
        os << endl;
        if (i == 0) {
            pop_indent();
            os << indent() << N_XPCOM_IMPL_RAW_METHOD_ACCESS << ":" << endl;
            push_indent();
        }

        method_decl* method = itf->methods[i];

        // Check for mismatched [in out] flags
        BOOST_FOREACH(param_decl* param, method->params)
        {
            check_primary_param(param);
            check_sub_param(param);
        }
        if (method->is_result_retval_param()) {
            param_decl* param = method->result;
            check_primary_param(param);
            check_sub_param(param);
        }

        do_method(os, method);
    }

    // class body end
    pop_indent();

    // Print using _object;
    os << endl;
    os << indent() << "protected:" << endl;

    push_indent();
    os << indent() << "using "
       << get_interface_impl_type(itf->base) << "::" << N_FUNC_object << ";"
       << endl;
    pop_indent();

    // class end
    os << indent() << "};" << endl;
}

void output_impl_header::do_method(ostream& os, method_decl* method)
{
    os << indent();
    do_method_signature(os, method);
    os << endl;

    os << indent() << "{" << endl;
    push_indent();

    do_method_body(os, method);

    pop_indent();
    os << indent() << "}" << endl;
}

void output_impl_header::do_method_signature(ostream& os, method_decl* method)
{
    print_method_signature(os, method, method_proxied/* | method_impl*/);
}

void output_impl_header::do_method_body(ostream& os, method_decl* method)
{
    do_method_param_check(os, method);

    // XPCOM_IMPL_METHOD_BEGIN
    if (!method->is_notxpcom()) {
        os << indent() << N_XPCOM_IMPL_METHOD_BEGIN << endl;
        os << indent() << "{" << endl;
        push_indent();
    } else {
        os << indent() << "// Notice:" << endl;
        os << indent()
           << "//  The exceptions are not catched for the [notxpcom] method."
           << endl;
        os << indent()
           << "//  You should not leave any exception"
              " across the interface boundary."
           << endl;
        os << endl;
    }

    // Comment of calling method
    os << indent() << "// ";
    do_impl_method_signature(os, method);
    os << ";" << endl;

    // Object call
    os << indent();

    if (method->is_notxpcom() && !method->result->type.is_void())
        os << "return ";

    if (method->is_result_retval_param()) {
        if (param_needs_retval_decl(method->result)) {
            do_param_arg(os, method->result, true);
            os << ";" << endl;
            os << indent() << method->result->name << "_";
        } else {
            do_param_arg(os, method->result);
        }
        os << " = ";
    }

    os << N_FUNC_object << "()->" << get_proxy_method_name_cpp(method) << "(";

    size_t param_count = 0;
    for (size_t i = 0; i < method->params.size(); ++i) {
        param_decl* param = method->params[i];
        if (!is_proxy_param_sub(param) || param->is_iid_is()) {
            if (param_count++ != 0)
                os << ", ";
            do_param_arg(os, param);
        }
    }

    os << ");" << endl;

    // XPCOM_IMPL_METHOD_END
    if (!method->is_notxpcom()) {
        pop_indent();
        os << indent() << "}" << endl;
        os << indent() << N_XPCOM_IMPL_METHOD_END << endl;

        os << endl;
        os << indent() << "return " << N_NS_OK << ";" << endl;
    }
}

void output_impl_header::do_method_param_check(ostream& os, method_decl* method)
{
    // Loop first to see if any thing needs to be checked
    vector<param_decl*> check_params;

    BOOST_FOREACH(param_decl* param, method->params)
    {
        if (param_needs_check(param))
            check_params.push_back(param);
    }
    if (method->is_result_retval_param() && param_needs_check(method->result)) {
        check_params.push_back(method->result);
    }

    // Do not write any thing if nothing needs to be checked
    if (check_params.empty())
        return;

    // Write param argument checkings
    os << "#ifndef " << N_XPCOM_DISABLE_METHOD_ARG_CHECKING << endl;

    os << indent() << "if (" << N_XPCOM_UNLIKELY << "(";
    for (size_t i = 0; i < check_params.size(); ++i) {
        if (i != 0)
            os << " || ";
        os << "!" << check_params[i]->name;
    }
    os << "))" << endl;
    push_indent();
    os << indent() << "return " << N_NS_ERROR_NULL_POINTER << ";" << endl;
    pop_indent();

    os << "#else" << endl;
    //os << "#else // " << N_XPCOM_DISABLE_METHOD_ARG_CHECKING << endl;

    BOOST_FOREACH(param_decl* param, check_params)
    {
        os << indent() << N_XPCOM_ASSERT << "(" << param->name << ");" << endl;
    }

    os << "#endif" << endl;
    //os << "#endif // " << N_XPCOM_DISABLE_METHOD_ARG_CHECKING << endl;
    os << endl;
}

void output_impl_header::do_param_arg(
        ostream& os, param_decl* param, bool write_retval)
{
    string retval;
    if (write_retval)
        retval = " " + param->name + "_";

    if (is_proxy_param_primary(param)) {
        if (param->type.is_array) {
            if (param->type.is_interface())
                os << N_ptr_array;
            else
                os << N_array;
            os << "_";

            if (is_param_out_isize2(param)) {
                os << N_out"_"N_isize2
                   << "<" << get_proxy_array_type_basename(&param->type) << ">"
                   << retval << "(" << param->name << ", "
                   << param->size_is->name << ")";
            } else {
                os << get_param_in_out_str(param);

                if (param->size_is == param->length_is) {
                    os << N_2 << "<"
                       << get_proxy_array_type_basename(&param->type) << ">"
                       << retval << "(" << param->name << ", "
                       << param->size_is->name << ")";
                } else {
                    os << "<"
                       << get_proxy_array_type_basename(&param->type) << ">"
                       << retval << "(" << param->name << ", "
                       << param->size_is->name << ", "
                       << param->length_is->name << ")";
                }
            }

        } else if (param->type.is_pstring()) {
            if (param->type.tag == type_decl::tag_pstring)
                os << N_pstr;
            else
                os << N_pwstr;
            os << "_";

            if (is_param_out_isize2(param)) {
                os << N_out"_"N_isize2 << retval << "(" << param->name
                   << ", " << param->size_is->name << ")";
            } else {
                BOOST_ASSERT(param->has_out() && "pstring_size: not out/inout param");
                os << get_param_in_out_str(param) << "_";

                if (param->size_is == param->length_is) {
                    os << N_size2 << retval << "("
                       << param->name << ", "
                       << param->size_is->name << ")";
                } else {
                    os << N_size << retval << "("
                       << param->name << ", "
                       << param->size_is->name << ", "
                       << param->length_is->name << ")";
                }
            }

        } else if (param->is_qi()) {
            os << N_ptr << "_" << get_param_in_out_str(param) << "_"
               << N_void << retval << "(" << param->name << ")";

        } else
            BOOST_ASSERT(false && "bad primary type");

    } else if (param->type.tag == type_decl::tag_bool) {
        os << N_bool << "_" << get_param_in_out_str(param)
           << retval << "(" << param->name << ")";

    } else if (param->type.tag == type_decl::tag_pstring) {
        os << N_pstr << "_" << get_param_in_out_str(param)
           << retval << "(" << param->name << ")";

    } else if (param->type.tag == type_decl::tag_pwstring) {
        os << N_pwstr << "_" << get_param_in_out_str(param)
           << retval << "(" << param->name << ")";

    } else if (param->type.tag == type_decl::tag_interface) {
        os << N_ptr << "_" << get_param_in_out_str(param)
           << retval << "(" << param->name << ")";

    } else if (param->type.tag == type_decl::tag_interface_qi_out) {
        os << N_ptr << "_" << get_param_in_out_str(param) << "_" << N_void
           << retval << "(" << param->name << ")";

    } else if (param->type.tag == type_decl::tag_uuid_ptr) {
        os << N_uuid_ptr << "_" << get_param_in_out_str(param)
           << retval << "(" << param->name << ")";

    } else {
        if (param->has_out() && !param->type.is_dipper())
            os << "*";
        os << param->name;
    }
}

void output_impl_header::do_impl_method_signature(
        ostream& os, method_decl* method)
{
    if (method->is_notxpcom()) {
        os << get_param_type_name(method->result);
    } else {
        os << get_proxy_param_type_name(method->result);
    }
    os << " ";

    os << get_proxy_method_name_cpp(method) << "(";

    size_t param_count = 0;
    for (size_t i = 0; i < method->params.size(); ++i) {
        param_decl* param = method->params[i];
        if (!is_proxy_param_sub(param) || param->is_iid_is()) {
            if (param_count++ != 0)
                os << ", ";
            os << get_proxy_param_type_name(param);
            os << " " << param->name;
        }
    }

    os << ")";
}

bool output_impl_header::param_needs_check(param_decl* param) const
{
    return param->has_out() && !param->type.is_dipper();
}

bool output_impl_header::param_needs_retval_decl(param_decl* param) const
{
    if (param->is_out() && param->is_retval()) {
        type_decl* type = &param->type;
        // ptr is not
        if (type->is_array || type->is_pstring() ||
            type->tag == type_decl::tag_bool ||
            type->tag == type_decl::tag_uuid_ptr)
        {
            return true;
        }
    }

    return false;
}

} // namespace xpgen
